require 'socket'
require 'thread'
require 'documenttransferprotocol'

class DocumentTransferClient
  attr_accessor :host
  attr_accessor :src_host

  def initialize(host, src_host)
    @host = host
    @src_host = src_host
    @response_data_size = 0
    @response_data = ""
    @client_socket = nil
    @lock = Mutex.new
    @node_id_hash = nil
  end

  def start_node_id_relation
    @lock.lock

    begin
      @node_id_hash = {}
    ensure
      @lock.unlock
    end
  end

  def add_node_id_relation(node_id, succ_id)
    @lock.lock

    begin
      if @node_id_hash[node_id] == nil
        @node_id_hash[node_id] = [succ_id]
        return true
      else
        @node_id_hash[node_id].each { |item| return false if item == succ_id }
        @node_id_hash[node_id] << succ_id
        return true
      end
    ensure
      @lock.unlock
    end
  end

  def remove_node_id_relation(node_id, succ_id)
    @lock.lock

    begin
      if @node_id_hash[node_id] != nil
        @node_id_hash[node_id].delete_if { |item| item == succ_id }
        if @node_id_hash[node_id].length == 0
          @node_id_hash.delete(node_id)
        end
      end
    ensure
      @lock.unlock
    end
  end

  def contain_node_id(node_id)
    @lock.lock

    begin
      if @node_id_hash[node_id] != nil
        return true
      else
        return false
      end
    ensure
      @lock.unlock
    end
  end

  def contain_node_id_relation(node_id, succ_id)
    @lock.lock

    begin
      if @node_id_hash[node_id] != nil
        @node_id_hash[node_id].each { |item| return true if item == succ_id }
        return false
      else
        return false
      end
    ensure
      @lock.unlock
    end
  end

  def qtd_node_id_relation(node_id)
    @lock.lock

    begin
      if @node_id_hash[node_id] != nil
        return @node_id_hash[node_id].length
      else
        return 0
      end
    ensure
      @lock.unlock
    end
  end

  def qtd_node_id
    @lock.lock

    begin
      return @node_id_hash.length
    ensure
      @lock.unlock
    end
  end

  def connect
    @lock.lock
    
    begin
      @client_socket = TCPSocket::new(@host, PROTOCOL_PORT)    
    rescue Exception => e
      puts "[DocumentTransferClient] Connection error: " + e.message
      return false
    ensure
      @lock.unlock
    end
    return true
  end

  def get_all_documents(node_id)
    @lock.lock

    begin
      return nil if !filter_by_node_id(node_id)

      flush_vars

      return nil if @client_socket == nil
      return nil if !send_basic_request(GET_ALL_DOCUMENTS, "0" * ID_LENGTH, node_id, 0)
      return nil if !receive_basic_response
      return nil if @response_data_size == 0
      return nil if !receive_response_data

      return @response_data
    ensure
      @lock.unlock
    end
  end

  def replicate_documents(document, node_id)
    @lock.lock

    begin
      return false if !filter_by_node_id(node_id)

      flush_vars

      return false if @client_socket == nil
      return false if !send_basic_request(REPLICATE_DOCUMENTS, "0" * ID_LENGTH, node_id, document.length)
      return false if !send_document(document.length, document)
      return false if !receive_basic_response

      return true
    ensure
      @lock.unlock
    end
  end

  def insert_documents(document, node_id)
    @lock.lock

    begin
      return false if !filter_by_node_id(node_id)

      flush_vars

      return false if @client_socket == nil
      return false if !send_basic_request(INSERT_DOCUMENTS, "0" * ID_LENGTH, node_id, document.length)
      return false if !send_document(document.length, document)
      return false if !receive_basic_response

      return true
    ensure
      @lock.unlock
    end
  end

  def destroy_replica(node_id)
    @lock.lock

    begin
      return false if !filter_by_node_id(node_id)

      flush_vars

      return false if @client_socket == nil
      return false if !send_basic_request(DESTROY_REPLICA, "0" * ID_LENGTH, node_id, 0)
      return false if !receive_basic_response

      return true
    ensure
      @lock.unlock
    end
  end

  def get_document(document_id, node_id)
    @lock.lock

    begin
      return nil if !filter_by_node_id(node_id)

      flush_vars

      return nil if @client_socket == nil
      return nil if !send_basic_request(GET_DOCUMENT, document_id, node_id, 0)
      return nil if !receive_basic_response
      return nil if @response_data_size == 0
      return nil if !receive_response_data
      return @response_data
    ensure
      @lock.unlock
    end
  end

  def insert_document(document_id, document, node_id)
    @lock.lock

    begin
      return false if !filter_by_node_id(node_id)

      flush_vars

      return false if @client_socket == nil
      return false if !send_basic_request(INSERT_DOCUMENT, document_id, node_id, document.length)
      return false if !send_document(document.length, document)
      return false if !receive_basic_response

      return true
    rescue Exception => e
      puts "[DocumentTransferClient] Excecao ao inserir: " + e.message
      return false
    ensure
      @lock.unlock
    end   
  end

  def update_document(document_id, document, node_id)
    @lock.lock

    begin
      return false if !filter_by_node_id(node_id)

      flush_vars

      return false if @client_socket == nil
      return false if !send_basic_request(UPDATE_DOCUMENT, document_id, node_id, document.length)
      return false if !send_document(document.length, document)
      return false if !receive_basic_response

      return true
    ensure
      @lock.unlock
    end
  end

  def remove_document(document_id, node_id)
    @lock.lock

    begin
      return false if !filter_by_node_id(node_id)

      flush_vars

      return false if @client_socket == nil
      return false if !send_basic_request(REMOVE_DOCUMENT, document_id, node_id, 0)
      return false if !receive_basic_response

      return true
    ensure
      @lock.unlock
    end
  end

  def close
    @lock.lock

    begin
      @client_socket.close
    ensure
      @lock.unlock
    end
  end

private
  def filter_by_node_id(node_id)
    return true if @node_id_hash == nil

    if @node_id_hash[node_id] != nil
      return true
    else
      return false
    end
  end

  def send_basic_request(oper_id, document_id, node_id, document_size)
    packed = oper_id.to_s + ";" + document_id + ";" + node_id + ";" + document_size.to_s + ";" + @src_host

    begin
      @client_socket.puts(packed)
    rescue
      return false
    end

    return true
  end

  def send_document(document_size, document)
    sent = 0

    while sent < document_size
      begin
        result = @client_socket.send(document[sent..-1], 0)
      rescue
        return false
      end

      return false if result == nil

      sent += result
    end

    return true
  end

  def receive_basic_response
    begin
      data = @client_socket.readline
    rescue
      return false
    end

    return false if data == nil

    array = data.split(";")

    @status = Integer(array[0])
    @response_data_size = Integer(array[1])

    return false if @status != REQ_OK

    return true
  end

  def receive_response_data
    received = 0

    while received < @response_data_size
      remaining = @response_data_size - received

      begin
        result = @client_socket.read(remaining)
      rescue Exception => e
        puts "Erro no read" + e.message
        return false
      end      

      return false if result == nil

      @response_data += result
      received += result.length
    end

    return true
  end
  
  def flush_vars
    @response_data_size = 0
    @response_data = ""
  end
end